#include "menu.h"
#include "winassert.h"

using std::runtime_error;
using sys::system_error;
using sys::runtime_error_ex;

namespace win
{
	// menu item info is an internal class used to
	// simpify menu setting oprations
  	class MenuItemInfo : public MENUITEMINFO
	{
		public:
			enum {MASK_DATA = MIIM_DATA};
		public:
			MenuItemInfo();
			void SetType(unsigned int value);
			void SetText(const char * text);
			void SetId(unsigned int value);
			void SetDataPtr(void *);
			void SetValues(const char * text, unsigned int id, void * data = NULL)
			{
				SetText(text);
				SetId(id);
    			SetDataPtr(data);
			}
			void SetSubMenu(HMENU SubMenu);
			
			void SetRetrieveMask(unsigned int value);
			void * GetDataPtr()
			{
				return (void *) dwItemData;
			}
			
			void Get(const HMENU handle, const int commandId);
			void Set(const HMENU handle, const int commandId);
	};
	
	void MenuItemInfo::Get(const HMENU handle, const int commandId)	
	{
	  	if (!::GetMenuItemInfo(handle,commandId,FALSE,this))
	  		throw system_error(::GetLastError());
  	}		

	void MenuItemInfo::Set(const HMENU handle, const int commandId)	
	{
	  	if (!::SetMenuItemInfo(handle,commandId,FALSE,this))
	  		throw system_error("Cannot set menu item info");
  	}		

	MenuItemInfo::MenuItemInfo()
	{
		::ZeroMemory(this,sizeof(MENUITEMINFO));
		cbSize = sizeof(MENUITEMINFO);
	}
	
	void MenuItemInfo::SetText(const char * text)
	{
		fMask |= MIIM_TYPE;
	   	fType |= MFT_STRING;
	   	dwTypeData = const_cast<char*>(text);
	}

	void MenuItemInfo::SetType(unsigned int type)
	{
	   	fType = type;
		fMask |= MIIM_TYPE;
	}

	void MenuItemInfo::SetDataPtr(void * p)
	{
		fMask |= MIIM_DATA;
	   	dwItemData = reinterpret_cast<DWORD>(p);
	}

	void MenuItemInfo::SetId(unsigned int id)
	{
		fMask |= MIIM_ID;
		wID = id;
	}
	
	void MenuItemInfo::SetSubMenu(HMENU sub)
	{
		fMask |= MIIM_SUBMENU;
		hSubMenu = sub;
	}
	
	void MenuItemInfo::SetRetrieveMask(unsigned int value)
	{
		fMask = value;
	}
}

namespace win
{

	Menu::Menu()
	{
		_handle = 0;
	}

	Menu::Menu(HMENU handle)
	{
		ASSERT(handle);
		_handle = handle;
	}

	void Menu::EnableMenuItemById(unsigned int id, bool enable)
	{
		EnableMenuItem(_handle,id, MF_BYCOMMAND | (enable?MF_ENABLED:MF_GRAYED));
	}

	Menu::Menu(HWND window_handle)
	{
		_handle = GetMenu(window_handle);
		if (_handle == NULL)
			throw runtime_error("Window does not have a menu!");
	}

	Menu Menu::SubMenuAt(const int pos)
	{
		ASSERT(_handle);
		HMENU sm = ::GetSubMenu(_handle,pos);
		if (sm == NULL)
		{
			throw runtime_error("Menu does not exist or is not a submenu");
		}
		return Menu(sm);
	}


	void * Menu::GetItemData(const int CommandId)
	{
		MenuItemInfo inf;
		inf.SetRetrieveMask(MenuItemInfo::MASK_DATA);
		inf.Get(_handle,CommandId);
		return inf.GetDataPtr();

	}
	

	/*
	void Menu::GetMenuItemById(unsigned int id, Menu::MenuItemInfo &result)
	{
	}
	*/
	
	int Menu::Count()
	{
		ASSERT(_handle);
		return ::GetMenuItemCount(_handle);
	}
	
	void Menu::RemoveAllMenus()
	{
		ASSERT(_handle);
  	  	int lastMenuPos = Count() - 1;
 	 	while (lastMenuPos != -1)
		{
		 	::DeleteMenu(_handle,lastMenuPos,MF_BYPOSITION);
		 	lastMenuPos--;
	 	}
	 	ASSERT(Count()==0);
	}

	
	PopupMenu::PopupMenu()
	{
		_handle = ::CreatePopupMenu();
	}
	
	void PopupMenu::Track(int x, int y, HWND receiver)
	{
		ASSERT(_handle);
		if (!TrackPopupMenu(_handle,TPM_LEFTALIGN,x,y,0,receiver,NULL))
		{
			const LONG error = GetLastError();
			if (error == 1446L || error==87 || error==0 || error==5)
			{
				// already active, so no damage done
				return;
			}
			throw system_error(error);
		}
	}
	
	void PopupMenu::Destroy()
	{
		ASSERT(_handle);
		DestroyMenu(_handle);
		_handle = 0;
	}
	
	int Menu::AppendItem(const int CommandId, const char * Text)
	{
		ASSERT(_handle);
		MenuItemInfo inf;
		inf.SetText(Text);
		inf.SetId(CommandId);
		const int pos = Count();
		if (::InsertMenuItem(_handle,pos,TRUE, &inf) == 0)
			throw system_error(GetLastError());
		return pos;	
	}

	int Menu::AppendSeperator()
	{
		ASSERT(_handle);
		MenuItemInfo inf;
		inf.SetType(MFT_SEPARATOR);
		const int pos = Count();
		if (::InsertMenuItem(_handle,pos,TRUE, &inf) == 0)
			throw system_error(GetLastError());
		return pos;	
	}
	
	int Menu::AppendItem(const int CommandId, const char * Text, void * data)
	{
		ASSERT(_handle);
		MenuItemInfo inf;
		inf.SetText(Text);
		inf.SetId(CommandId);
		inf.SetDataPtr(data);
		const int pos = Count();
		if (::InsertMenuItem(_handle,pos,TRUE, &inf) == 0)
			throw system_error(GetLastError());
		return pos;	
	}
	

	void Menu::SetItemSubMenu(const int commandId, const Menu& m)
	{
		ASSERT(_handle);
		ASSERT(m._handle);
		ASSERT(_handle != m._handle); // tryng to set submenu to self

		MenuItemInfo inf;
		inf.SetSubMenu(m._handle);
		inf.Set(_handle,commandId);
	}

}
